from khronos.utils.deque import Deque
from khronos.utils.misc import INF

class Checkable(object):
    """Class implementing an observable variable, i.e. these objects may be monitored for changes 
    automatically using Check objects. A Check consists of a condition function, which is a boolean 
    function that receives the variable's value and verifies a particular condition on the value. 
    When the condition function returns True, an activation function is called with the satisfying 
    value as argument. Additionally, checks have a priority attribute which defines the order in 
    which they are called when the variable's value is changed. 
    A 'sticky' attribute is also available. This attribute is used to define the check's behavior 
    on activation. A non-sticky check is removed from the variable's list of checks when it is 
    satisfied, whereas a sticky check remains in the list. This means that a sticky check may 
    activate any number of times, and a non-sticky check activates at most once."""
    def __init__(self, value=None):
        self.__value = value
        self.__checks = Deque()
        
    def __str__(self):
        return "%s<%d checks>(%s)" % (self.__class__.__name__, len(self.__checks), self.__value)
        
    def clear(self):
        self.__checks.clear()
        self.__value = None
        
    def get(self):
        return self.__value
        
    def set(self, value):
        self.__value = value
        if len(self.__checks) > 0:
            self.check()
            
    def insert(self, check):
        if check.condition(self.__value):
            if check.sticky:
                self.__checks.insort(check)
            check.activation(self.__value)
        else:
            self.__checks.insort(check)
            check.__checkable = self
            
    def remove(self, check):
        if check.__checkable is not self:
            raise ValueError("check not found in Checkable object")
        self.__checks.outsort(check, exact=True)
        check.__checkable = None
        
    def check(self):
        """Evaluate checks with the current value of this object. Checks are evaluated from high 
        to low priority. Checks removed by the activation of a previous check are not evaluated."""
        lo = 0
        for check in list(self.__checks):
            if check.__checkable is self and check.condition(self.__value):
                if not check.sticky:
                    lo = self.__checks.outsort(check, lo=lo, exact=True)
                    check.__checkable = None
                check.activation(self.__value)
                
    # --------------------------------------------
    # Check factory functions
    def less_than(self, value, activation, priority=0.0, sticky=False, insert=True):
        def condition(v):
            return v < value
        condition.__name__ = "< %s" % (value,)
        check = Check(condition, activation, priority, sticky)
        if insert:
            self.insert(check)
        return check
        
    def less_or_equal(self, value, activation, priority=0.0, sticky=False, insert=True):
        def condition(v):
            return v <= value
        condition.__name__ = "<= %s" % (value,)
        check = Check(condition, activation, priority, sticky)
        if insert:
            self.insert(check)
        return check
        
    def greater_than(self, value, activation, priority=0.0, sticky=False, insert=True):
        def condition(v):
            return v > value
        condition.__name__ = "> %s" % (value,)
        check = Check(condition, activation, priority, sticky)
        if insert:
            self.insert(check)
        return check
        
    def greater_or_equal(self, value, activation, priority=0.0, sticky=False, insert=True):
        def condition(v):
            return v >= value
        condition.__name__ = ">= %s" % (value,)
        check = Check(condition, activation, priority, sticky)
        if insert:
            self.insert(check)
        return check
        
    def equal_to(self, value, activation, priority=0.0, sticky=False, insert=True):
        def condition(v):
            return v == value
        condition.__name__ = "== %s" % (value,)
        check = Check(condition, activation, priority, sticky)
        if insert:
            self.insert(check)
        return check
        
    def not_equal_to(self, value, activation, priority=0.0, sticky=False, insert=True):
        def condition(v):
            return v != value
        condition.__name__ = "!= %s" % (value,)
        check = Check(condition, activation, priority, sticky)
        if insert:
            self.insert(check)
        return check
        
    def member_of(self, value, activation, priority=0.0, sticky=False, insert=True):
        def condition(v):
            return v in value
        condition.__name__ = "in %s" % (value,)
        check = Check(condition, activation, priority, sticky)
        if insert:
            self.insert(check)
        return check
        
    def not_member_of(self, value, activation, priority=0.0, sticky=False, insert=True):
        def condition(v):
            return v not in value
        condition.__name__ = "not in %s" % (value,)
        check = Check(condition, activation, priority, sticky)
        if insert:
            self.insert(check)
        return check
        
    def contains(self, value, activation, priority=0.0, sticky=False, insert=True):
        def condition(v):
            return value in v
        condition.__name__ = "contains %s" % (value,)
        check = Check(condition, activation, priority, sticky)
        if insert:
            self.insert(check)
        return check
        
    def not_contains(self, value, activation, priority=0.0, sticky=False, insert=True):
        def condition(v):
            return value not in v
        condition.__name__ = "not contains %s" % (value,)
        check = Check(condition, activation, priority, sticky)
        if insert:
            self.insert(check)
        return check
        
class Check(object):
    """Check objects are used to execute arbitrary code when a Checkable variable's value meets a 
    given condition. Please refer to the documentation of the Checkable class for more details."""
    def __init__(self, condition, activation, priority=0.0, sticky=False):
        self.condition = condition
        self.activation = activation
        self.priority = priority
        self.sticky = sticky
        
    def __cmp__(self, check):
        return -cmp(self.priority, check.priority)
        
    def __str__(self):
        return "%s<%s%s>(%s if %s)" % (self.__class__.__name__, self.priority, 
                                       ", S" if self.sticky else "", 
                                       self.activation.__name__, 
                                       self.condition.__name__)
                                       
